// YAL zeldan

//tab=4

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

#ifdef __FreeBSD__
#include <sys/ioctl.h>
#include <sys/disk.h>
#endif


#define APPLY(M,X) M(X)
#define STR(X) #X
#define ZERO(X) memset(&X, 0, sizeof(X))


#ifdef __LP64__
	#define OF "%zu"
#else
	#define OF "%llu"
#endif



static const char * COPYRIGHT =
 "Copyright (C)  2005-2014  Daniele Zelante <daniele.zelante@gmail.com>";

const char * zeldan()
{
	return
		"\033[1;31mz"
		"\033[1;33me"
		"\033[1;32ml"
		"\033[1;36md"
		"\033[1;34ma"
		"\033[1;35mn"
		"\033[0;39m"
	;
}

const char * yal()
{
    return "YAL (yet another license)";
}

void banner()
{
	fprintf(stderr, "product   : bdz \n");
	fprintf(stderr, "version   : %s \n", APPLY(STR,GITVER));
	fprintf(stderr, "author    : %s \n", zeldan());
	fprintf(stderr, "email     : <daniele.zelante@gmail.com> \n");
	fprintf(stderr, "\n");
}

void license()
{
	fprintf(stderr, "license : \n");
	fprintf(stderr, "%s \n", yal());
	fprintf(stderr, "%s \n\n", COPYRIGHT);
}



void syntax()
{
	fprintf(stderr,
	 "syntax   : bdz {-r[z]|-w[s]} [-m msize] [-b bsize] [-V] filename \n\n");
	fprintf(stderr,
	 "-r[z]    : read from file, write to stdout ('z' to write sparse) \n");
	fprintf(stderr,
	 "-w[s]    : read from stdin, write to file ('s' to write sync) \n");
	fprintf(stderr,
	 "-m msize : set maximum size in KiB to read or write \n");
	fprintf(stderr,
	 "-b bsize : set blocksize in KiB to read or write \n");
	fprintf(stderr,
	 "-V       : display version info \n");
	fprintf(stderr,
	 "-L       : display license info \n");
	fprintf(stderr,
	 "filename : block device or regular file to work on \n");
	fprintf(stderr, "\n");
	exit(3);
}


void myfatal(const char * str)
{
	fprintf(stderr, "%s \n", str);
	exit(2);
}

void myerror(const char * str)
{
	perror(str);
	exit(1);
}

void logerror(const char * str)
{
	perror(str);
}

size_t gcd(size_t a, size_t b)
{
	size_t c;
	while (a) c=a, a=b%a, b=c;
	return b;
}

size_t lcm(size_t a, size_t b)
{
	return a/gcd(a,b)*b;
}


off_t min_off(off_t x, off_t y)
{
	return (x<y) ? x : y;
}


off_t read_ioe(const int fd, char * buf, size_t count, size_t redublock)
{
	const off_t base = lseek(fd, 0, SEEK_CUR);

	size_t done = 0;
	const int rr = read(fd, buf, count);
	if (rr<0 && errno==EIO)
	{
		fprintf(stderr,
		 "read_ioe base="OF " count=%zu : EIO \n", base, count);

		if (count<redublock)
		{
			memset(buf, '\0', count);
			done = 1;
		} else
		{
			const size_t count2 = count/2;
			const size_t count2x = count - count2;
			if (count2x)
			{
				lseek(fd, base+count2, SEEK_SET);
				done += read_ioe(fd, buf+count2, count2x, redublock);
			}
	
			if (count2)
			{
				lseek(fd, base, SEEK_SET);
				done += read_ioe(fd, buf, count2, redublock);
			}
		}
	}
	else
	{
		done += rr;
	}
	lseek(fd, base + done, SEEK_SET);
	return done;
}


size_t read_block(const int fd, char * buf, const size_t count)
{
	size_t done = 0;
	while (done<count)
	{
		const off_t rd = read(fd, buf+done, count-done);
		if (rd<=0)
		{
			logerror("\nerror: read");
			break;
		}
		const size_t rdu = (size_t)rd;
		assert(rdu<=count);
		if (rdu < count-done)
			fprintf(stderr,
			 "\nwarn: slow read (%zu < %zu) \n", rdu, count-done);
		done += rdu;
	}
	return done;
}

size_t read_stream(const int fd, char * buf, const size_t count)
{
	size_t done = 0;
	while (done<count)
	{
		const off_t rd = read(fd, buf+done, count-done);
		if (rd<=0)
		{
			logerror("\nerror: read");
			break;
		}
		const size_t rdu = (size_t)rd;
		assert(rdu<=count);
		done += rdu;
	}
	return done;
}


size_t write_n(const int fd, const char * buf, const size_t count)
{
	size_t done = 0;
	while (done<count)
	{
		const off_t wr = write(fd,buf+done,count-done);
		if (wr==-1) logerror("\nerror: write");
		if (wr<=0) break;
		const size_t wru = (size_t)wr;
		if (wru < count-done)
			fprintf(stderr,
			 "\nwarn: slow write (%zu < %zu) \n", wru, count-done);
		done += wru;
	}
	return done;
}




void printsize(const off_t size)
{
	const double sizeK = (double)size/1024.0;
	const double sizeM = sizeK/1024.0;
	const double sizeG = sizeM/1024.0;

	fprintf(stderr,
	 "size = "OF " B | %.02f KiB | %.02f MiB | %.02f GiB \n",
       	 size, sizeK, sizeM, sizeG);
}

void printblksize(blksize_t file, blksize_t stream, size_t x)
{
	fprintf(stderr,
	 "blksize: file = "OF ", stream = "OF ", transfer = "OF" \n",
		(size_t)file, (size_t)stream, x);
}


void printprogress(
 time_t timebase, const off_t total, const off_t part, off_t * s0, time_t * t0)
{
	const time_t t1 = time(0);

	if ((t1!=*t0) || (part==total))
	{
		const int progress = (int) ((100.0*part) / total + 0.5);
		const double speed = (double) (part-*s0)/(t1-*t0);
		const double speedM = speed/(1024.0*1024.0);

		const double avgspeed = (double) (part) / (t1-timebase);
		const double avgspeedM = avgspeed/(1024.0*1024.0);

		int eta = (int) ((total-part) / avgspeed);
		const int etaD = eta / (60*60*24);
		eta -= etaD * (60*60*24);
		const int etaH = eta / (60*60);
		eta -= etaH * (60*60);
		const int etaM = eta / 60;
		eta -= etaM * 60;
		const int etaS = eta;

		if (part!=total)
		fprintf(stderr,
		 "\r %2d%% : ETA = %dd,%02dh,%02dm,%02ds"
		 " : AVG_RATE = %.02f MiB/s : RATE = %.02f MiB/s     \r",
			progress,etaD,etaH,etaM,etaS,avgspeedM,speedM);
		else
			fprintf(stderr,
			 "\r %2d%% : ETA = %dd,%02dh,%02dm,%02ds"
			 " : AVG_RATE = %.02f MiB/s"
			 " : complete               \r",
				progress,etaD,etaH,etaM,etaS,avgspeedM);

		*s0 = part;
		*t0 = t1;
	}

}

struct fileinfo_t
{
	int _ok;
	int _regular;
	int _sizeable;
	off_t _size;
	blksize_t _blksize;
};


struct fileinfo_t fileinfo(const int fd)
{
	struct fileinfo_t fi; ZERO(fi);

	struct stat s;
	if (-1==fstat(fd,&s)) myerror("fstat");

	fi._ok = (0
		|| S_ISREG(s.st_mode)
		|| S_ISBLK(s.st_mode)
		|| S_ISCHR(s.st_mode)
		|| S_ISFIFO(s.st_mode)
		) && !isatty(fd);
	fi._regular = S_ISREG(s.st_mode);
	fi._size = s.st_size;
	fi._blksize = s.st_blksize;
	if (fi._size) fi._sizeable = !0;

	#ifdef __FreeBSD__
	if (fi._ok && !fi._regular && !fi._sizeable)
	{
		if (-1!=ioctl(fd,DIOCGMEDIASIZE,&fi._size))
			fi._sizeable = !0;
	}
	#endif


	if (!fi._sizeable)
	{
		int ok = !0;
		const off_t x = lseek(fd,0,SEEK_END);
		if (x==-1) ok=0;
		if (-1==lseek(fd,0,SEEK_SET)) ok=0;
		if (ok)
		{
			fi._size = x;
			if (fi._size) fi._sizeable = !0;
		}
	}

	return fi;
}



int isdirty(const char * buf, const size_t len)
{
	for (size_t n=0; n<len; ++n)
		if (buf[n]) return !0;
	return 0;
}


void blockread(
	const char * filename,
	const off_t maxsize,
	const blksize_t blksize,
	int sparse)
{
	const struct fileinfo_t nfo1 = fileinfo(1);
	if (!nfo1._ok)
		myfatal("invalid stdout");

	if (!nfo1._regular && sparse)
	{
		fprintf(stderr,
		 "warn: ignoring [-z] when writing to non-regular file \n");
		sparse = 0;
	}

	const int fdi = open(filename,O_RDONLY);
	if (fdi==-1) myerror("open");

	const struct fileinfo_t fdinfo = fileinfo(fdi);
	if (!fdinfo._ok) myfatal("invalid input file type");

	off_t srcsize = 0;

	if (!fdinfo._sizeable)
	{
		if (maxsize)
			srcsize = maxsize;
		else
			myfatal("[-m size]"
			 " is mandatory when source file size is unknown");
	}
	else
	{
		srcsize = fdinfo._size;

		if (maxsize)
		{
			if (maxsize > srcsize)
				fprintf(stderr,
				 "warn: requested size greater than real size \n");
			if (maxsize < srcsize)
			{
				fprintf(stderr,
				 "info: truncating size from "OF " to "OF " \n",
				 srcsize, maxsize);
				srcsize = maxsize;
			}
		}
	}

	const size_t blocksize = lcm(blksize, lcm(fdinfo._blksize, nfo1._blksize));

	printsize(srcsize);
	printblksize(fdinfo._blksize, nfo1._blksize, blocksize);
	char * buffer = malloc(blocksize);
	const time_t timebase = time(0);
	time_t t0 = timebase;
	off_t oldpart = 0;
	off_t part = 0;

	while (part<srcsize)
	{
		printprogress(timebase,srcsize,part,&oldpart,&t0);

		const size_t rdreq = min_off((off_t)blocksize, srcsize-part);
		size_t rd = read_block(fdi, buffer, rdreq);

		if (rd<rdreq)
		{
			fprintf(stderr, "\nerror: unexpected end of device \n");
			break;
		}

		part += rd;

		if (sparse && rd==blocksize && part<srcsize && !isdirty(buffer,rd))
		{
			if(-1==lseek(1,rd,SEEK_CUR))
				myerror("\nseek");
		}
		else
		{
			const size_t wr = write_n(1,buffer,rd);
			if (wr<rd)
			{
				fprintf(stderr, "\nerror: unable to write \n");
				break;
			}
		}

	}
	printprogress(timebase, srcsize, part, &oldpart, &t0);

	free(buffer);

}


void blockwrite(
	const char * filename,
	const off_t maxsize,
	const blksize_t blksize,
	int syncr)
{
	const int fdo = open(filename,O_RDWR|(syncr ? O_SYNC : 0));
	if (fdo==-1) myerror("open");

	const struct fileinfo_t fdonfo = fileinfo(fdo);
	if (!fdonfo._ok) myfatal("invalid output file type \n");

	const struct fileinfo_t nfo0 = fileinfo(0);
	if (!nfo0._ok)
		myfatal("invalid stdin");

	off_t devsize = 0;

	if (!fdonfo._sizeable && !nfo0._sizeable && !maxsize)
		myfatal("[-m size]"
		" is mandatory when both target and stream sizes are unknown");

	if (fdonfo._sizeable && nfo0._sizeable)
	{
		if (fdonfo._size!=nfo0._size)
			fprintf(stderr,
			 "warn: target size is "OF
			 ", stream size is "OF
             ", using smaller one \n",
			 fdonfo._size, nfo0._size);
		devsize = min_off(fdonfo._size,nfo0._size);
	}

	if (fdonfo._sizeable && !nfo0._sizeable) devsize=fdonfo._size;
	if (!fdonfo._sizeable && nfo0._sizeable) devsize=nfo0._size;


	if (maxsize)
	{
		if (maxsize > devsize)
			fprintf(stderr, "warn: requested size greater than real size \n");
		if (maxsize < devsize)
		{
			fprintf(stderr,
			 "info: truncating size from "OF" to "OF" \n", devsize, maxsize);
			devsize = maxsize;
		}
	}

	const size_t blocksize = lcm(blksize, lcm(fdonfo._blksize, nfo0._blksize));
	printsize(devsize);
	printblksize(fdonfo._blksize, nfo0._blksize, blocksize);
	char * buffer = malloc(blocksize);
	const time_t timebase = time(0);
	time_t t0 = timebase;
	off_t oldpart = 0;
	off_t part = 0;

	while (part<devsize)
	{
		printprogress(timebase,devsize,part,&oldpart,&t0);

		const size_t rdreq = (size_t)(min_off((off_t)blocksize, devsize-part));
		const size_t rd = read_stream(0, buffer, rdreq);
		if (rd<rdreq)
		{
			fprintf(stderr, "\nerror: unexpected end of file \n");
			break;
		}

		part += rd;

		const size_t wr = write_n(fdo, buffer, rd);
		if (wr<rd)
		{
			fprintf(stderr, "\nerror: unable to write \n");
			break;
		}

		if (rd<blocksize) break;

	}
	printprogress(timebase,devsize,part,&oldpart,&t0);

	free(buffer);
}


int main(int argc, char ** argv)
{
	assert(sizeof (off_t)==8);

	const char * optstring = "LVrwszb:m:";
	int writeop = 0;
	int readop = 0;
	int syncr = 0;
	int sparse = 0;
	int banneropt = 0;
	int licenseopt = 0;
	off_t maxsize = 0;
	blksize_t blksize = 1;

	for (;;)
	{
		const int opt = getopt(argc,argv,optstring);
		if (opt==-1) break;
		if (opt=='V') banneropt = !0;
		if (opt=='L') licenseopt = !0;
		if (opt=='r') readop = !0;
		if (opt=='w') writeop = !0;
		if (opt=='s') syncr = !0;
		if (opt=='z') sparse = !0;

		if (opt=='m')
		{
			if (maxsize!=0) syntax();
			maxsize = ((off_t) (atoi(optarg))) * 1024;
			if (maxsize==0) syntax();
		}
		
		if (opt=='b')
		{
			if (blksize!=1) syntax();
			blksize = ((blksize_t) (atoi(optarg))) * 1024;
			if (blksize==0 || blksize==1) syntax();
		}
	}

	if (banneropt) banner();
	if (licenseopt) license();

	if (optind+1!=argc) syntax();
	const char * filename = argv[optind];

	if (readop==writeop) syntax();
	if ((sparse && writeop) || (readop && syncr)) syntax();

	fprintf(stderr, "%s: %s ", readop ? "reading" : "writing", filename);

	if (maxsize)
		fprintf(stderr, " ; maxsize "OF" KiB", maxsize/1024);
	if (syncr)
		fprintf(stderr, " ; sync");

	fprintf(stderr, "\n");

	if (readop)
		blockread(filename, maxsize, blksize, sparse);
	if (writeop)
		blockwrite(filename, maxsize, blksize, syncr);

	fprintf(stderr, "\nOK \n");

	return 0;
}


//.
